package br.ufpb.di.ppgi.persistencia.pagination;

import java.util.Collections;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HibernateQueryPagedList<T> {
	private static final Logger LOGGER = LoggerFactory.getLogger(HibernateQueryPagedList.class);
	//--------------------------------------------------- SIMPLE PROPERTIES
	private final DetachedCriteria dc;
	private final Session session;
	
	private Integer maxPages = Integer.MAX_VALUE;
	private boolean cacheable = false;
	
	//--------------------------------------------------- CONSTRUCTORS
	public HibernateQueryPagedList(DetachedCriteria dc, Session session) {
		this(dc,session,Integer.MAX_VALUE);
	}
	
	public HibernateQueryPagedList(DetachedCriteria dc, Session session, int maxPages) {
		this.dc = dc;
		this.session = session;
		this.maxPages = maxPages;
	}

	public HibernateQueryPagedList<T> setCacheable(boolean cacheable) {
		this.cacheable = cacheable;
		return this;
	}

	@SuppressWarnings("unchecked")
	private List<T> list(Integer startPosition,Integer maxResults) {
		Criteria c = dc.getExecutableCriteria(session);
		if (maxResults != null)
			c.setMaxResults(maxResults);
		if (startPosition != null)
			c.setFirstResult(startPosition);
		
		try {
			if (cacheable)
				c.setCacheable(cacheable);
			return c.list();
		} catch (Exception ex) {
			LOGGER.error("Erro na paginacao: ",ex);
			return Collections.<T>emptyList();
		}
	}
	
	private void disableLimites(DetachedCriteria dc, int max) {
		dc
		.getExecutableCriteria(session)
		.setMaxResults(max)
		.setFirstResult(0);
	}

	public PagedList<T> query(int maxResultOnPage, Integer page) {
		
		page = page == null || page < 1 ? 1 : page;
		List<T> list = list((page-1)*maxResultOnPage, maxResultOnPage);
		
		int max = maxPages == Integer.MAX_VALUE ? maxPages : maxPages*maxResultOnPage;
		
		if (max < 0)
			max = maxPages;
		
		disableLimites(dc,max);
		int numberOfResults = count(dc).intValue();
		
		
		return new PagedList<T>(
				list,
				Math.min(max,numberOfResults),
				maxResultOnPage,
				page);
	}
	
	public PagedList<T> query(int maxResultOnPage) {
		return query(maxResultOnPage,null);
	}
	
	//-------------------------------------------------- PRIVATE STUFF
	public Long count(DetachedCriteria dc) {
		Long result = ((Number)dc
			.getExecutableCriteria(session)
			.setProjection(Projections.rowCount())
			.uniqueResult()).longValue();
		dc.setProjection(null);
		return result;
	}	
}
